import { DbTableMarkdown } from '../../models/dbTableMarkdown'
import { FileHelper } from '../utils/fileHelper'
import { TextHelper } from '../utils/textHelper'

export class MarkdownWriter {
	/**
	 * Render an array to a markdown table
	 * - Support object
	 * @param items array values
	 */
	private static renderArray(items: any[]): string {
		if (!items || items.length === 0) {
			return ''
		}

		const obj = items[0]
		if (Array.isArray(obj)) {
			return MarkdownWriter.render2dArray(items)
		}

		return MarkdownWriter.renderObjectArray(items)
	}

	/**
	 * Render an array to a markdown table without headers
	 * @param items array values
	 * @returns a string of table without headers
	 */
	private static render2dArray(items: any[][]): string {
		const headers: string[] = []
		const rows: string[][] = []
		let length = 0

		items.forEach((item) => {
			if (item.length > length) {
				length = item.length
			}
			const row: string[] = []
			item.forEach((value) => {
				const cell = MarkdownWriter.renderValue(value)
				row.push(cell)
			})
			rows.push(row)
		})

		// init empty headers
		for (let i = 0; i < length; i += 1) {
			headers.push('')
		}
		return MarkdownWriter.toMarkdownTable(headers, rows)
	}

	/**
	 * Render an array to a markdown table with headers
	 * @param items array values
	 * @returns a string of table with headers
	 */
	private static renderObjectArray(items: any[]): string {
		const obj = items[0]
		const headers: string[] = []
		const rows: string[][] = []
		Object.keys(obj).forEach((key) => {
			const renderredKey = MarkdownWriter.renderKey(key)
			headers.push(renderredKey)
		})
		items.forEach((item) => {
			const row: string[] = []
			Object.keys(obj).forEach((key) => {
				const cell = MarkdownWriter.renderValue(item[key])
				row.push(cell)
			})
			rows.push(row)
		})

		return MarkdownWriter.toMarkdownTable(headers, rows)
	}

	/**
	 * Render an array to a markdown table
	 * @param headers array values of table headers
	 * @param rows 2-d array values of table rows
	 * @returns a string of a markdown table
	 */
	private static toMarkdownTable(headers: string[], rows: string[][]): string {
		// get headers and rows and width
		const columnsWidth: number[] = []
		headers.forEach((header) => {
			let width: number = 1
			const cellWidth = TextHelper.width(header)
			if (cellWidth > width) {
				width = cellWidth
			}
			columnsWidth.push(width)
		})
		rows.forEach((row) => {
			row.forEach((cell, columnIndex) => {
				const width: number = columnsWidth[columnIndex]
				const cellWidth = TextHelper.width(cell)
				if (cellWidth > width) {
					columnsWidth[columnIndex] = cellWidth
				}
			})
		})

		let table: string = ''
		let headerText = '|'
		let headerLine = '|'
		// pad headers and rows
		headers.forEach((header, index) => {
			const width = columnsWidth[index]
			headerText += ` ${TextHelper.padEnd(header, width, ' ')} |`
			headerLine += ` ${''.padEnd(width, '-')} |`
		})
		table += `${headerText}\n`
		table += `${headerLine}\n`

		rows.forEach((row) => {
			let rowText = '|'
			row.forEach((cell, index) => {
				const width = columnsWidth[index]
				rowText += ` ${TextHelper.padEnd(cell, width, ' ')} |`
			})
			table += `${rowText}\n`
		})

		return table
	}

	private static renderKey(key: string): string {
		if (!key) {
			return ''
		}

		let value: string = key
		for (let i = 0; i < value.length; i += 1) {
			// TODO: handle non-Basic-Multilingual-Plane
			const currentCode = value.charCodeAt(i)
			if (i === 0) {
				value = `${value.charAt(0).toUpperCase()}${value.substring(1)}`
			} else {
				const prevCode = value.charCodeAt(i - 1)
				const preIsLowerCase = prevCode >= 97 && prevCode <= 122
				const curIsCapitalCase = currentCode >= 65 && currentCode <= 90
				if (preIsLowerCase && curIsCapitalCase) {
					value = `${value.substring(0, i)} ${value.substring(i)}`
				}
			}
		}
		value = `\`${value}\``
		return value
	}

	private static renderObject(obj: any, level: number = 1): string {
		let data: string = ''
		let listItems: string = ''
		let objects: string = ''

		Object.keys(obj).forEach((propKey) => {
			const propValue = obj[propKey]
			if (Array.isArray(propValue)) {
				objects += `\n${''.padEnd(level + 1, '#')} ${MarkdownWriter.renderKey(propKey)}\n`
				const subData = MarkdownWriter.renderArray(propValue)
				if (subData.length > 0) {
					objects += `\n${subData}`
				}
			} else if (propValue !== null && typeof propValue === 'object') {
				objects += `\n${''.padEnd(level + 1, '#')} ${MarkdownWriter.renderKey(propKey)}\n`
				const subData = MarkdownWriter.renderObject(propValue)
				if (subData.length > 0) {
					objects += `\n${subData}`
				}
			} else {
				const text = MarkdownWriter.renderValue(propValue)
				listItems += `- ${MarkdownWriter.renderKey(propKey)}`
				listItems += text.length > 0 ? `:${text}\n` : ':\n'
			}
		})
		if (listItems.length > 0) {
			data += `${listItems}`
		}
		data += objects
		return data
	}

	public static renderTable(table: any, markdownPath: string): string {
		let data: string = `# Table: ${table.name}\n`
		const newTable = MarkdownWriter.onMarkdown(table)
		const subData = MarkdownWriter.renderObject(newTable)
		if (subData.length > 0) {
			data += `${subData}`
		}
		if (markdownPath) {
			FileHelper.writeFileSync(markdownPath, data)
		}
		return data
	}

	public static renderValue(value: any): string {
		if (value === null) {
			return '`null`'
		}
		if (value === undefined) {
			return '``'
		}
		if (typeof value === 'boolean') {
			return value? '`Yes`': '`No`'
		}
		if (typeof value !== 'string') {
			return `\`${value.toString()}\``
		}

		const text = TextHelper.encodeMarkdown(value)

		// TODO: get escaped text
		return text
	}

	/**
	 * Convert the dbTable object to markdown table for serializing
	 * @param table the db table
	 */
	private static onMarkdown(table: any): DbTableMarkdown {
		return {
			table: [
				{
					name: table.name,
					comment: table.comment,
					characterSet: table.characterSet,
					collation: table.collation,
					engine: table.engine,
				},
			],
			primaryKey: table.primaryKey ? [table.primaryKey] : [],
			indexes: table.indexes,
			foreignKeys: table.foreignKeys,
			columns: table.columns,
		}
	}
}
